using System;
using System.Globalization;
using Kopernikus.Collections.Generic;
using Kopernikus.Parsing.GenericLexing;
using Kopernikus.Parsing.GenericParsing;
using Kopernikus.Parsing.Lexing;
using NUnit.Framework;

namespace Kopernikus.Parsing.Tests.GenericParsing
{
    [TestFixture]
    public class TestUnambigousMath
    {
        [LexIgnore("\\s+")]
        private class AlmostUnambigousMathParser : IDisambiguator
        {
            #region IDisambiguator Members

            Production IDisambiguator.Disambiguate(Set<Production> productions)
            {
                return null;
            }

            #endregion

            [CfgStart("P -> E")]
            [Cfg("E -> add")]
            [Cfg("add -> mul")]
            [Cfg("mul -> exp")]
            [Cfg("exp -> T")]
            [Cfg("T -> num")]
            private static double p_main(double e)
            {
                return e;
            }

            [Cfg("add -> add + mul")]
            private static double p_add(params double[] args)
            {
                return args[0] + args[2];
            }

            [Cfg("add -> add - mul")]
            private static double p_sub(params double[] args)
            {
                return args[0] - args[2];
            }

            [Cfg("mul -> mul * exp")]
            private static double p_mul(params double[] args)
            {
                return args[0]*args[2];
            }

            [Cfg("mul -> mul / exp")]
            private static double p_div(params double[] args)
            {
                return args[0]/args[2];
            }

            [Cfg("exp -> exp ^ T")]
            private static double p_exp(double d1, double d2, double d3)
            {
                return Math.Pow(d1, d3);
            }

            [Cfg("T -> ( E )")]
            private static double p_par(params double[] args)
            {
                return args[1];
            }

            // NOTE: Negation rule causes ambiguity
            [Cfg("T -> - E")]
            private static double p_neg(params double[] args)
            {
                return -args[1];
            }

            [Lex("num", "\\d+(\\.\\d+)?")]
            private static double t_num(LexerMatch m)
            {
                return double.Parse(m.Value, NumberStyles.Number, CultureInfo.GetCultureInfo(1033));
            }

            [LexDefault(LexDefaultAttributeOptions.AnySingleCharacter)]
            private static double t_default(LexerMatch m)
            {
                return 0;
            }
        }

        [Test]
        public void Test()
        {
            GenericParser<AlmostUnambigousMathParser, double> parser =
                GenericParser<AlmostUnambigousMathParser, double>.Create(new AlmostUnambigousMathParser());

            Assert.AreEqual(7, parser.Parse("1+2*3"));
            Assert.AreEqual(7, parser.Parse("2*3+1"));

            Assert.AreEqual(9, parser.Parse("(1+2)*3"));
            Assert.AreEqual(27, parser.Parse("3*(1+2)*3"));

            Assert.AreEqual(22, parser.Parse("2*3+2^4"));
            Assert.AreEqual(22, parser.Parse("2^4+2*3"));

            Assert.AreEqual(10, parser.Parse("2^4+2*-3"));

            Assert.AreEqual(1, parser.Parse("2^-4*((1+2+3+4)*3 / 2 / 3 + 20 - 3^2)"));
        }
    }
}